package com.yst.webplugin.component;

import com.yst.spring.context.SpringContext;
import com.yst.util.ClassUtils;
import com.yst.web.utils.UrlUtil;
import com.yst.webplugin.annotation.*;
import com.yst.webplugin.exception.ExceptionNotSupportResultType;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class ControllerUtil {

    private final static Map<String,Object> controllerMap=new HashMap<>();
    private final static Map<String, Field> fieldMap=new HashMap<>();
    private final static List<Class<? extends Annotation>> fieldMethodClassList=new ArrayList<>();
    private static String contextPath="";

    private static void fetchContextPath(ApplicationContext context){
        Environment environment = context.getEnvironment();
        String[] paths=Constant.CONST_CONTEXT_SEARCH_PATH.split(Constant.CONST_SPLIT_CHAR);
        for(String path:paths){
            String result=environment.getProperty(path,"");
            if (!result.isEmpty()){
                contextPath=result;
                return;
            }
        }
        contextPath="/";
    }

    public static String getContextPath(ApplicationContext context){
        Environment environment = context.getEnvironment();
        String[] paths=Constant.CONST_CONTEXT_SEARCH_PATH.split(Constant.CONST_SPLIT_CHAR);
        for(String path:paths){
            String result=environment.getProperty(path,"");
            if (!result.isEmpty()){
                return result;
            }
        }
        return "/";
    }

    private static void appendFieldPathMap(List<String> controllerPathList,String fieldName,Field field){
        for (String cPath:controllerPathList){
            fieldMap.put(UrlUtil.comboPath(contextPath,cPath,fieldName),field);
        }
    }
    private static boolean fieldIsMethod(Field field){
        Annotation[] annotations=field.getDeclaredAnnotations();
        for (Annotation annotation:annotations){
            if (fieldMethodClassList.contains(annotation.annotationType())){
                return true;
            }
        }
        return false;
    }
    private static void fetchAllFieldMethods(ApplicationContext context){
        Map<String,Object> map=context.getBeansWithAnnotation(RestController.class);
        controllerMap.putAll(map);
        for (Object obj:map.values()){
            RequestMapping requestMapping= obj.getClass().getAnnotation(RequestMapping.class);
            if (Objects.nonNull(requestMapping)){
                Map<String,Field> allFields= ClassUtils.getClassFields(obj.getClass());
                if (Objects.nonNull(allFields)) {
                    for (String fieldName:allFields.keySet()) {
                        Field field=allFields.get(fieldName);
                        List<String> pathList=new ArrayList<>();
                        pathList.addAll(Arrays.asList(requestMapping.path()));
                        pathList.addAll(Arrays.asList(requestMapping.value()));
                        if (fieldIsMethod(field)){
                            appendFieldPathMap(pathList,fieldName,field);
                        }
                    }
                }
            }
        }
    }
    public static Field getFieldByUrl(String url){
        String path=UrlUtil.formatUrl(url);
        if (fieldMap.containsKey(path)){
            return fieldMap.get(path);
        }
        return null;
    }

    private final static SpringContext.IContextReady contextReady= context -> {
        fetchParams();
    };
    private static void fetchParams(){
        if (!fieldMap.isEmpty()) return;
        if (Objects.nonNull(SpringContext.getContext())) {
            fetchContextPath(SpringContext.getContext());
            fetchAllFieldMethods(SpringContext.getContext());
        }
    }

    private static final Map<Class<? extends Annotation>,List<Class<?>>> resultTypeCache=new ConcurrentHashMap<>();

    private static final Map<Field,Class<? extends Annotation>> fieldAnnotationClassCache=new ConcurrentHashMap<>();

    /**
     * check result type of field
     * @param field source field
     */
    public static void checkResultType(Field field) throws ExceptionNotSupportResultType{
        Class<? extends Annotation> annotationClass=fieldAnnotationClassCache.computeIfAbsent(field,(key)->{
            for(Class<? extends Annotation> a:fieldMethodClassList){
                Annotation annotation=field.getAnnotation(a);
                if (Objects.nonNull(annotation)){
                    return a;
                }
            }
            return null;
        });


        if (Objects.nonNull(annotationClass)){
            ResultType resultType= annotationClass.getAnnotation(ResultType.class);
            if (resultType.returnModelClass()){
                try {
                    Method method=annotationClass.getMethod("modelClass");
                    Class<?> clazz = (Class<?>) method.invoke(field.getAnnotation(annotationClass));
                    if (clazz.equals(field.getType())){
                        return;
                    }
                } catch (Exception ignore) {

                }
            }
        }

        List<Class<?>> typeList=resultTypeCache.computeIfAbsent(annotationClass,(key)->{
            if (Objects.isNull(key)) return null;
            ResultType resultType= key.getAnnotation(ResultType.class);
            if (Objects.nonNull(resultType)) {
                return Arrays.asList(resultType.resultTypes());
            }
            return null;
        });

        if (Objects.isNull(typeList)||!typeList.contains(field.getType())){
            throw new ExceptionNotSupportResultType(field.getType(),field);
        }
    }

    static {
        fieldMethodClassList.addAll(Arrays.asList(
                Create.class,
                Command.class,
                Delete.class,
                GetModelByKey.class,
                Modify.class,
                Query.class,
                QueryTable.class,
                Query.BatchQuery.class,
                Query.BatchModelQuery.class,
                QuerySingleRow.class,
                Command.BatchCommand.class
        ));
        SpringContext.addOnReadyListener(contextReady);
    }
}
